﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Xml.Serialization;

namespace zEngine.GameObjs
{
    public class zObject : DrawableGameComponent
    {

        private Model _model;
        private Texture2D _texture;
        private string _modelName;
        private string _textureName;

        private float _size;

        private float _scale = 1;
        private Vector3 _rotation = Vector3.Zero;
        private Vector3 _position = Vector3.Zero;

        private Matrix[] _boneTransformation = null;
        private Matrix[] _boneAnimation = null;

        private BoundingSphere _boundingSphere;
        private BoundingBox _boundingBox;

        private bool _lODEnabled = false;
        private int _lOD = 0;
        private List<Model> _models;

        private bool _isContainSkeleton;
        private List<Matrix> _bindPose;
        private List<Matrix> _inverseBindPose;
        private List<int> _skeletonHierarchy;
        private Dictionary<string,int> _boneIndices;

        private zShader _shader;

        #region Properties
        public string TextureName
        {
            get
            {
                return _textureName;
            }
            set { _textureName = value; }
        }
        public string ModelName
        {
            get
            {
                return _modelName;
            }
            set { _modelName = value; }
        }
        [XmlIgnore]
        public Texture2D Texture
        {
            get { return _texture; }
            set { _texture = value; }
        }
        [XmlIgnore]
        public Model Model
        {
            get { return _model; }
            set { _model = value; }
        }

        [XmlIgnore]
        public BoundingBox BoundingBox
        {
            get 
            { 
                return zModel.TransformBoundingBox(_boundingBox, ModelTransformation); 
            }
            set { _boundingBox = value; }
        }
        [XmlIgnore]
        public BoundingSphere BoundingSphere
        {
            get { return zModel.TransformBoundingSphere(_boundingSphere, ModelTransformation); }
            set { _boundingSphere = value; }
        }
        [XmlIgnore]
        public Matrix[] BoneTransformation
        {
            get
            {
                if (_boneTransformation == null)
                {
                    if (Model != null)
                    {
                        _boneTransformation = zModel.BonesTransformation(Model);
                    }
                }
                return _boneTransformation;
            }
            set
            {
                _boneTransformation = zModel.BonesTransformation(Model);
            }
        }
        [XmlIgnore]
        public Matrix[] BoneAnimation
        {
            get
            {
                return _boneAnimation;
            }
            set
            {
                _boneAnimation = value;
            }
        }
        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }
        public Vector3 Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }
        public float Scale
        {
            get { return _scale; }
            set { _scale = value; }
        }
        public float Size
        {
            get { return _size; }
            set { _size = value; }
        }
        [XmlIgnore]
        public List<Model> Models
        {
            get { return _models; }
            set { _models = value; }
        }
        [XmlIgnore]
        public bool LODEnabled
        {
            get { return _lODEnabled; }
            set { _lODEnabled = value; }
        }
        [XmlIgnore]
        public int LOD
        {
            get { return _lOD; }
            set { _lOD = value; }
        }
        [XmlIgnore]
        Matrix ModelTransformation
        {
            get
            {
                return Matrix.CreateScale(_scale) * 
                    Matrix.CreateFromYawPitchRoll(_rotation.Y, _rotation.X, _rotation.Z) *
                    Matrix.CreateTranslation(_position);
            }
        }
        [XmlIgnore]
        public zShader Shader
        {
            get { return _shader; }
            set { _shader = value; }
        }
        [XmlIgnore]
        public Dictionary<string, int> boneIndices
        {
            get { return _boneIndices; }
            set { _boneIndices = value; }
        }
        [XmlIgnore]
        public List<int> SkeletonHierarchy
        {
            get { return _skeletonHierarchy; }
            set { _skeletonHierarchy = value; }
        }
        [XmlIgnore]
        public List<Matrix> InverseBindPose
        {
            get { return _inverseBindPose; }
            set { _inverseBindPose = value; }
        }
        [XmlIgnore]
        public List<Matrix> BindPose
        {
            get { return _bindPose; }
            set { _bindPose = value; }
        }
        [XmlIgnore]
        public bool IsContainSkeleton
        {
            get { return _isContainSkeleton; }
            set
            {
                _isContainSkeleton = value;
            }
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the zObject class.
        /// </summary>
        public zObject(Game game) : base (game)
        {
        }
        public zObject(Game game, Model model)
            : base(game)
        {
            LoadModel(model);
        }
        public zObject(Game game, Model model, float size) : base (game)
        {
            LoadModel(model);
            _size = size;
            Resize();
        }

        #region DrawableComponent override
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            // if this object don't use custom effect, then just draw it with normal effect
            if (Shader == null)
            {
                DrawWithBasicEffect();
                return;
            }

            // Draw with custom effect
            Cameras.Camera cam = Cameras.Camera.GetMainCamera(Game);
            Model drawModel = _model;

            // If we enable Level of detail, then we will draw the current LOD instead of main model
            if (_lODEnabled && _lOD > -1 && _lOD < _models.Count)
            {
                drawModel = _models[_lOD];   
            }

            _boneTransformation = zModel.BonesTransformation(drawModel);

            // If found model, then draw it
            if (drawModel != null)
            {
                foreach (ModelMesh mesh in drawModel.Meshes)
                {
                    foreach (ModelMeshPart part in mesh.MeshParts)
                    {
                        Shader.World = _boneAnimation[mesh.ParentBone.Index] * _boneTransformation[mesh.ParentBone.Index] * ModelTransformation;
                        Shader.View = cam.ViewMatrix;
                        Shader.Projection = cam.ProjectionMatrix;
                        Shader.DiffuseMap = Texture;
                        Shader.SetParameter("Bones", _boneAnimation);
                        Shader.Update();
                        part.Effect = Shader.Effect;
                    }
                    mesh.Draw();
                }
            }
            if (Settings.EnableDrawBoundingBox)
                DrawBoundingBox();
            base.Draw(gameTime);
        }

        public void DrawWithBasicEffect()
        {
            Cameras.Camera cam = Cameras.Camera.GetMainCamera(Game);
            Model drawModel = _model;

            // If we enable Level of detail, then we will draw the current LOD instead of main model
            if (_lODEnabled && _lOD > -1 && _lOD < _models.Count)
            {
                drawModel = _models[_lOD];
            }

            _boneTransformation = zModel.BonesTransformation(drawModel);

            // If found model, then draw it
            if (drawModel != null)
            {
                foreach (ModelMesh mesh in drawModel.Meshes)
                {
                    foreach (BasicEffect eff in mesh.Effects)
                    {
                        if (Texture != null)
                        {
                            eff.TextureEnabled = true;
                            eff.Texture = Texture;
                        }
                        eff.EnableDefaultLighting();
                        eff.World = _boneAnimation[mesh.ParentBone.Index] * _boneTransformation[mesh.ParentBone.Index] * ModelTransformation;
                        eff.View = cam.ViewMatrix;
                        eff.Projection = cam.ProjectionMatrix;

                    }
                    mesh.Draw();
                }
            }
            if (Settings.EnableDrawBoundingBox)
                DrawBoundingBox();
        }
        #endregion

        public void DrawBoundingBox()
        {
            DrawPrimatives.DrawBoundingBox(BoundingBox, Color.GreenYellow, Cameras.Camera.GetMainCamera(Game), Game.GraphicsDevice);
        }

        #region Methods
        /// <summary>
        /// Load model to object
        /// </summary>
        public void LoadModel(string modelName)
        {
            _modelName = modelName;
            LoadModel(Game.Content.Load<Model>(_modelName));
        }
        public void LoadModel(Model model)
        {
            _model = model;
            
            // Get the model tag see if it contains skeleton information
            ExtendedObjs.ModelTag tag = _model.Tag as ExtendedObjs.ModelTag;
            if (tag != null)
            {
                BindPose = tag.BindPose;
                InverseBindPose = tag.InverseBindPose;
                SkeletonHierarchy = tag.SkeletonHierarchy;
                boneIndices = tag.BoneIndices;
                IsContainSkeleton = true;
            }
            else
                IsContainSkeleton = false;

            // Prepare neccesary information for the model
            _boneTransformation = zModel.BonesTransformation(_model);
            _boundingSphere = zModel.CreateBoundingSphere(_model, _boneTransformation);
            _boundingBox = zModel.CreateBoundingBox(_model, _boneTransformation);

            _boneAnimation = new Matrix[Model.Bones.Count];
            for (int i = 0; i < Model.Bones.Count; i++)
            {
                _boneAnimation[i] = Matrix.Identity;
            }
        }

        /// <summary>
        /// Resize the scale to fix with the size
        /// </summary>
        public void Resize()
        {
            if (_size != _boundingSphere.Radius * 2)
            {
                _scale = _size / (_boundingSphere.Radius * 2);
            }
        }
        #endregion
        
    }
}
